package com.crypt.properties.util;


import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Random;

/**
 * @author xiaolang
 * @version 1.0
 * @description AES 加密算法
 * @date 2019/6/29 11:20
 * <pre>
 *  支持PKCS7Padding填充
 *  依赖bcprov-jdk16
 * </pre>
 */
public class Aes {


    /**
     * 填充模式
     * 加密算法/加密模式/填充类型 AES/CBC/PKCS7Padding  AES/CBC/PKCS5Padding
     * PKCS7 支持 需要添加 bcprov-jdk16 jar依赖
     * ECB模式不需要偏移量
     */
    private static final String DEFAULT_ALGORITHM = "AES/CBC/PKCS7Padding";

    private static final int IV_LENGTH = 16;

    private static final int KEY_SIZE = 128;

    // 原始密钥必须是16的倍数
    // 密钥长度为 128 192 256 默认128
    // 1字节 = 8位 16 => 128
    private SecretKeySpec secretKey;
    private String algorithm;
    /**
     * 偏移量 必须是16位
     */
    private String iv;

    private Aes() {
    }

    private static String getRandomChars(int length) {
        if (length <= 0) {
            length = 16;
        }
        String str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+=/.*";
        int len = str.length();
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            int index = new Random().nextInt(len);
            sb.append(str.charAt(index));
        }
        return sb.toString();
    }


    public Aes(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        this(key, getRandomChars(IV_LENGTH));
    }

    public Aes(String key, String iv) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        this(key, DEFAULT_ALGORITHM, iv);
    }

    public Aes(String key, String algorithm, String iv) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        this.algorithm = algorithm;
        this.secretKey = this.getSecretKey(key);
        this.iv = iv == null || iv.length() == 0 ? getRandomChars(IV_LENGTH) : iv;
    }

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            // 支持PKCS7 填充模式
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 获取偏移量
     *
     * @return 偏移量
     */
    public String getIv() {
        return this.iv;
    }

    /**
     * 获取密钥 Base64 编码之后的
     *
     * @return 源密码
     */
    public String getKey() throws UnsupportedEncodingException {
        return new String(this.secretKey.getEncoded(), StandardCharsets.UTF_8);
    }


    /**
     * 获取偏移量参数
     *
     * @return 获取偏移量对象
     * @throws UnsupportedEncodingException 抛出不支持的编码异常
     */
    private IvParameterSpec getIvParameterSpec() throws UnsupportedEncodingException {
        return new IvParameterSpec(this.iv.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 获取密钥
     *
     * @param key 源密钥
     * @return 密钥对象
     * @throws UnsupportedEncodingException 抛出不支持的编码异常
     * @throws NoSuchAlgorithmException     抛出不存在的算法异常
     */
    private SecretKeySpec getSecretKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        SecretKeySpec secretKeySpec = null;
        if (key == null || key.length() == 0) {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(this.algorithm);
            keyGenerator.init(KEY_SIZE);
            // 生成密钥
            SecretKey secretKey = keyGenerator.generateKey();
            // 转换为AES 密钥
            secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), this.algorithm);
        } else {
            byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
            secretKeySpec = new SecretKeySpec(keyBytes, this.algorithm);
        }
        return secretKeySpec;

    }

    /**
     * 加密
     *
     * @param src 源数据
     * @return 加密(Base64后的字符串)
     * @throws NoSuchPaddingException             抛出不存在的填充方式异常
     * @throws NoSuchAlgorithmException           抛出不存在的算法异常
     * @throws UnsupportedEncodingException       抛出不支持的编码异常
     * @throws InvalidAlgorithmParameterException 抛出不合法的算法偏移量异常
     * @throws InvalidKeyException                抛出不合法的密钥异常
     * @throws BadPaddingException                抛出错误的填充方式异常
     * @throws IllegalBlockSizeException          抛出不合法的密码块大小异常
     */
    public String encrypt(String src) throws NoSuchPaddingException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // 获取密码器
        Cipher cipher = Cipher.getInstance(this.algorithm);
        // 获取偏移量
        IvParameterSpec iv = this.getIvParameterSpec();
        // 初始化为加密模式
        cipher.init(Cipher.ENCRYPT_MODE, this.secretKey, iv);
        // 加密
        byte[] encryptedBytes = cipher.doFinal(src.getBytes(StandardCharsets.UTF_8));
        // return Base64.encode(encryptedBytes);
        return HexBytes.toHex(encryptedBytes);
    }

    /**
     * 解密
     *
     * @param encrypted 加密后的数据
     * @return 返回解密后字符串
     * @throws NoSuchPaddingException             抛出不存在的填充方式异常
     * @throws NoSuchAlgorithmException           抛出不存在的算法异常
     * @throws UnsupportedEncodingException       抛出不支持的编码异常
     * @throws InvalidAlgorithmParameterException 抛出不合法的算法偏移量异常
     * @throws InvalidKeyException                抛出不合法的密钥异常
     * @throws BadPaddingException                抛出错误的填充方式异常
     * @throws IllegalBlockSizeException          抛出不合法的密码块大小异常
     */
    public String decrypt(String encrypted) throws NoSuchPaddingException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // 获取密码器
        Cipher cipher = Cipher.getInstance(this.algorithm);
        // 获取偏移量
        IvParameterSpec iv = this.getIvParameterSpec();
        // 初始化为解密模式
        cipher.init(Cipher.DECRYPT_MODE, this.secretKey, iv);
        // 解密
        // byte[] decryptedBytes = cipher.doFinal(Base64.decode(encrypted));
        byte[] decryptedBytes = cipher.doFinal(HexBytes.toBytes(encrypted));
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }

    public void setAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    public void setIv(String iv) {
        this.iv = iv;
    }
}

